package com.edu.javaproect.address;

import com.edu.javaproect.utils.Base58;
import com.edu.javaproect.utils.Hash;

import java.security.*;
import java.security.interfaces.ECPublicKey;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECPoint;
import java.util.Arrays;

/**
 * @author:秇稔
 * @email:2580108178@qq.com
 * @Date:2021/12/24 8:59
 * @desc：该类用于推导生成原始格式的比特币地址，并进行规则校验。
 **/
public class BitCoinAddress {

/**
 * 该方法用于随机生成一个EC算法的密钥对，并返回该密钥对
 * @return 密钥对对象
 */
    private KeyPair generateKey(){
        try {
            //椭圆曲线密钥对生成器
            KeyPairGenerator generator = KeyPairGenerator.getInstance("EC");
            //Parameter参数  Spec明确的/指定的  EC参数指定
            ECGenParameterSpec spec = new ECGenParameterSpec("secp256k1");
            //initialize初始化
            generator.initialize(spec);
            return generator.genKeyPair();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 该方法用于推到生成原始格式的比特币地址，并返回地址字符串
     * @param keyPair 随机的密钥对信息
     * @return 生成的比特币地址
     */
    private String generateAddress(KeyPair keyPair) {
        // TODO: 2021/12/25 推导原始格式地址
        //明确指出EC算法：ECPublicKey    publicKey：公钥密码学的公钥
        ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
        //得到的原始公钥 ： 04(1) + x坐标(32) + y坐标(32)
        byte[] pubKey  = toUnCompressPub(publicKey);

        //对公钥进行哈希计算
        //原始格式
        byte[] sha256 = Hash.SHA256(pubKey);
        byte[] pubhash = Hash.RIPEMD160(sha256);

        //再公钥哈希前面添加版本号
        byte[] version = new byte[1];
        version[0] = 0x00;

        byte[] ver_pubhash = new byte[version.length+pubhash.length];
        System.arraycopy(version,0,ver_pubhash,0,version.length);
        System.arraycopy(pubhash,0,ver_pubhash,version.length,pubhash.length);

        //对版本号_公钥哈希的序列化进行双哈希，取前四个字节
        byte[] hash1 = Hash.SHA256(ver_pubhash);
        byte[] hash2 = Hash.SHA256(hash1);

        byte[] check = new byte[4];
        System.arraycopy(hash2,0,check,0,check.length);

        //将check拼接ver_pubhash后面，组成新的序列
        byte[] ver_pubhash_check = new byte[ver_pubhash.length + check.length];
        System.arraycopy(ver_pubhash,0,ver_pubhash_check,0,ver_pubhash.length);
        System.arraycopy(check,0,ver_pubhash_check,ver_pubhash.length,check.length);

        //base58编码
        return Base58.encode(ver_pubhash_check);
    }


    /**
     * 该方法用于拼接得到非压缩格式(原始格式)的公钥，并返回原始公钥
     * @param publicKey ec公钥
     * @return 原始格式公钥
     */
    public byte[] toUnCompressPub(ECPublicKey publicKey){
        ECPoint point = publicKey.getW();
        byte[] xBytes = point.getAffineX().toByteArray();
        byte[] yBytes = point.getAffineX().toByteArray();

        //数组拷贝
        //原始格式结构 ： 04(1) + x坐标(32) + y坐标(32)
        byte[] pubKey = new byte[1+2*32];
        pubKey[0] = 04;

        //判断X坐标
        if (xBytes.length == 32){
            //拷贝谁  从哪里开始拷贝   拷贝到哪里去   拷贝放到哪里去   拷贝多少个
            System.arraycopy(xBytes,0,pubKey,1,xBytes.length);
        }else if (xBytes.length > 32){
            System.arraycopy(xBytes,1,pubKey,1,32);
        }

        //判断Y坐标
        if (yBytes.length == 32){
            //拷贝谁  从哪里开始拷贝   拷贝到哪里去   拷贝放到哪里去   拷贝多少个
            System.arraycopy(yBytes,0,pubKey,33,yBytes.length);
        }else if (yBytes.length > 32){
            System.arraycopy(yBytes,1,pubKey,33,32);
        }

        return pubKey;
    }

    /**
     * 生成一个比特币地址并返回
     * @return 返回生成的比特币地址
     */
    public String newAddress(){
        KeyPair keyPair =generateKey();
        return generateAddress(keyPair);
    }

    /**
     * 该方法用于校验比特币地址字符串是否符合规范，并返回结果
     * @param address 要检验的比特币地址
     * @return 是否符合规范
     */
    public boolean validataAddress(String address){
        if("".equals(address) || address == null){
            return false;
        }
        byte[] ver_pubhash_check = Base58.decode(address);
        if (ver_pubhash_check.length<4){
            return false;
        }
        //截取后四个字节，作为待验证的校验位
        byte[] check = new byte[4];
        System.arraycopy(ver_pubhash_check,
                ver_pubhash_check.length-4,
                 check,
                0,
                 check.length);

        byte[] ver_pubhash = new byte[ver_pubhash_check.length-4];
        System.arraycopy(ver_pubhash_check,
                0,
                ver_pubhash,
                0,
                ver_pubhash.length);
        //双hash
        byte[] hash1 = Hash.SHA256(ver_pubhash);
        byte[] hash2 = Hash.SHA256(hash1);

        byte[] check_sum = new byte[4];
        System.arraycopy(hash2,0,check_sum,0,check_sum.length);

        return Arrays.equals(check,check_sum);

    }

//    //临时测试使用
//    public static void main(String[] args){
//        BitCoinAddress address = new BitCoinAddress();
//        String str = address.newAddress();
//        System.out.println("地址生成：" + str);
//        boolean result = address.validataAddress(str);
//        System.out.println(result);
//    }
}
